<?php

namespace yunj\builder;

use yunj\Config;
use yunj\Validate;
use yunj\enum\BuilderType;
use yunj\control\YunjControl;

final class YunjForm extends Builder {

    protected $type = BuilderType::FORM;

    protected $builderValidateClass = "\\yunj\\validate\\YunjForm";

    protected $scriptFileList = ["/static/yunj/js/form.min.js?v=" . YUNJ_VERSION];

    protected $options = ["tab", "url", "field", "fieldValidate", "button", "load", "submit"];

    /**
     * 必填：表单切换栏（默认选中第一项）
     * 'tab'=>[
     *      'base'=>'基础',
     *      'other'=>'其他'
     *  ]
     * @var array
     */
    private $tab;

    /**
     * 可选：异步数据接口地址（默认当前地址）
     * "url"=>"xxx.com/base.html"
     * @var string
     */
    private $url;

    /**
     * 必填：表单字段
     * 'field'=>[
     *      'base'=>[],     // 切换栏tab=base的筛选表单（可选）
     *      'other'=>[      // 切换栏tab=other的筛选表单（可选）
     *          // 单行文本框
     *          'field_key'=>[
     *              'type'=>'text',         //（可选，默认text）
     *              'title'=>'field_label', //（可选）
     *              'placeholder'=>'',      //（可选）
     *              'value'=>'',            //默认值（可选）
     *          ],
     *      ]
     *  ]
     * @var array
     */
    private $field;

    // 表单字段验证器的回调方法
    private $fieldValidateCallback;

    /**
     * 可选：表格按钮（默认为空）
     * 'button'=>['back','reload','clear','submit']
     * @var array
     */
    private $button;

    // 数据加载回调方法
    private $loadCallback;

    // 数据提交回调方法
    private $submitCallback;

    protected function setAttr(array $args = []): void {
        parent::setAttr($args);
        $this->config = Config::get('form', []);
    }

    /**
     * Notes: 切换栏
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:05
     * @param callable|array|string|number $code
     * callable:
     *      function(){
     *          return ["base"=>"基础","other"=>"其他"];
     *      }
     * array:
     *      ["base"=>"基础","other"=>"其他"];
     * @param string $title
     * @return YunjForm
     */
    public function tab($code, string $title = '') {
        if ($this->existError()) return $this;
        if (!is_callable($code)) {
            $callable = function () use ($code, $title) {
                return is_array($code) ? $code : [$code => $title];
            };
        } else {
            $callable = $code;
        }
        $this->setOptionCallbale('tab', $callable);
        return $this;
    }

    /**
     * Notes: 执行切换栏回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:42
     * @param callable $callable
     * @return YunjForm
     */
    protected function exec_tab_callable(callable $callable) {
        $tab = $this->tab ? $this->tab : [];
        $result = call_user_func($callable);
        if (!is_array($result)) return $this->error("YunjForm [tab] 需返回有效数组");
        $this->tab = $result + $tab;
        return $this;
    }

    /**
     * Notes: 请求地址
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:41
     * @param callable|string $url
     * callable:
     *      function(){
     *          return "xxxx.com/xxx.html";
     *      }
     * string:
     *      xxxx.com/xxx.html
     * @return YunjForm
     */
    public function url($url) {
        if ($this->existError()) return $this;
        if (!is_callable($url)) {
            $callable = function () use ($url) {
                return $url;
            };
        } else {
            $callable = $url;
        }
        $this->setOptionCallbale('url', $callable);
        return $this;
    }

    /**
     * Notes: 执行请求地址回调
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 18:33
     * @param callable $callable
     * @return YunjForm
     */
    protected function exec_url_callable(callable $callable) {
        if ($this->existError()) return $this;
        $url = $callable();
        if (!is_string($url)) return $this->error("YunjForm [url] 需返回有效地址");
        $this->url = $url;
        return $this;
    }

    /**
     * Notes: 表单字段
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:08
     * @param callable $callable
     * @return YunjForm
     */
    public function field(callable $callable) {
        if ($this->existError()) return $this;
        $this->setOptionCallbale('field', $callable);
        return $this;
    }

    /**
     * Notes: 执行表单字段回调
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:10
     * @param callable $callable
     * @return YunjForm
     */
    protected function exec_field_callable(callable $callable) {
        if ($this->existError()) return $this;
        $field = $this->field ? $this->field : [];
        foreach ($this->tab as $tab => $title) {
            $tabField = $callable($tab);
            if (!is_array($tabField)) return $this->error("YunjForm [field] 需返回有效数组");
            $field[$tab] = isset($field[$tab]) ? $field[$tab] : [];
            $this->handleFieldTab($field[$tab], $tabField);
        }
        $this->field = $field;
        return $this;
    }

    /**
     * Notes: 表单字段的验证器
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:05
     * @param callable $callable
     * callable:
     *      function(){
     *          return new TestValidate();
     *      }
     * @return YunjForm
     */
    public function fieldValidate(callable $callable) {
        if ($this->existError()) return $this;
        $this->fieldValidateCallback = $callable;
        return $this;
    }

    /**
     * Notes: 处理切换栏对应的表单字段
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 11:25
     * @param array $field_tab
     * @param array $fields
     * @return YunjForm
     */
    protected function handleFieldTab(array &$field_tab, array $fields) {
        foreach ($fields as $key => $field) {
            if (!$field || !is_array($field)) return $this->error("YunjForm [field] 返回参数[{$key}]异常");
            $field_tab[$key] = YunjControl::field($key, $field)->args();
        }
        return $this;
    }

    /**
     * Notes: 表单按钮
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:12
     * @param callable|array $button
     * callable:
     *      function(){
     *          return ["back","reset","submit"];
     *      }
     * array:
     *      ["back","reset","submit"]
     * @return YunjForm
     */
    public function button($button) {
        if ($this->existError()) return $this;
        if (!is_callable($button)) {
            $callable = function () use ($button) {
                return $button;
            };
        } else {
            $callable = $button;
        }
        $this->setOptionCallbale('button', $callable);
        return $this;
    }

    /**
     * Notes: 执行表单按钮回调
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 18:33
     * @param callable $callable
     * @return YunjForm
     */
    protected function exec_button_callable(callable $callable) {
        if ($this->existError()) return $this;
        $this->button = $this->button ?: [];
        $button = $callable();
        if (!is_array($button)) return $this->error("YunjForm [button] 需返回有效数组");
        if (!array_in($button, $this->config['button']))
            return $this->error("YunjForm [button] 返回值不在有效范围=>" . json_encode($this->config['button']));
        $this->button = array_keys(array_flip($this->button) + array_flip($button));
        return $this;
    }

    /**
     * Notes: 数据获取
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:45
     * @param callable $callable
     * @return YunjForm
     */
    public function load(callable $callable) {
        if ($this->existError()) return $this;
        $this->loadCallback = $callable;
        return $this;
    }

    /**
     * Notes: 数据提交
     * Author: Uncle-L
     * Date: 2021/3/29
     * Time: 10:45
     * @param callable $callable
     * @return YunjForm
     */
    public function submit(callable $callable) {
        if ($this->existError()) return $this;
        $this->submitCallback = $callable;
        return $this;
    }

    /**
     * Notes: 视图配置
     * Author: Uncle-L
     * Date: 2021/10/8
     * Time: 16:32
     * @return array
     * @throws \Exception
     */
    protected function viewArgs() {
        $args = parent::viewArgs();
        if ($this->tab) {
            $args['tab'] = $this->tab;
        }
        if ($this->url) {
            $args['url'] = $this->url;
        }
        if ($this->field) {
            $args['field'] = $this->field;
        }
        if ($this->button) {
            $args['button'] = $this->button;
        }
        $args['load'] = $this->loadCallback ? true : false;
        return $args;
    }

    /**
     * Notes: 异步处理数据加载
     * Author: Uncle-L
     * Date: 2020/7/8
     * Time: 13:37
     * @return array
     */
    protected function async_load() {
        $response = call_user_func($this->loadCallback);
        return $response;
    }

    /**
     * Notes: 异步处理数据提交
     * Author: Uncle-L
     * Date: 2020/7/8
     * Time: 13:37
     * @param $param
     * @return array
     */
    protected function async_submit($param) {
        $this->execOptionsCallbale("submit");
        if (!$this->submitCallback) throw_error_json("YunjForm [submit] 未设置");
        // 验证
        $this->async_submit_validate($param);
        // 执行提交动作
        $response = call_user_func_array($this->submitCallback, [$param['data'], $param['tab_data']]);
        return $response;
    }

    /**
     * Notes: 异步处理数据提交验证
     * Author: Uncle-L
     * Date: 2020/9/17
     * Time: 11:51
     * @param $param
     * @return array
     */
    private function async_submit_validate(array &$param): array {
        $this->execOptionsCallbale("tab,field,fieldValidate");
        if ($this->existError()) throw_error_json($this->getError());
        if (!$this->field) return $param;
        $validate = $this->fieldValidateCallback ? call_user_func($this->fieldValidateCallback) : (new Validate());
        if (!($validate instanceof Validate)) throw_error_json("YunjForm [fieldValidate] 需返回 \\yunj\\Validate 实例对象");
        $fields = [];
        foreach ($this->field as $tab => $tab_fields) $fields += $tab_fields;
        $this->formDataValidate($validate, $fields, $param["data"], "submit");
        foreach ($param["tab_data"] as &$v) array_supp($param["data"], $v);
        return $param;
    }

}